﻿using System;
using System.IO;
using System.IO.Pipes;
using System.Threading.Tasks;

namespace Red.Core.Network
{
    /// <summary>
    /// NamedPipe
    /// </summary>
    public class RawNamedPipe
    {
        private PipeStream _writeStream = null;
        private PipeStream _readStream = null;

        private int _bufferSize;
        private string _pipeName;
        private bool _connected = false;
        private bool _opened = false;


        public bool IsOpened
        {
            get { return _opened; }
        }

        public bool IsConnected
        {
            get
            {
                if ( _readStream != null && _writeStream != null )
                    return _connected;

                return false;
            }
        }

        public int GetBufferSize()
        {
            return _bufferSize;
        }

        public RawNamedPipe( string pipeName, int bufferSize )
        {
            _bufferSize = bufferSize;
            _pipeName = pipeName;
        }

        public async Task<bool> Connect()
        {
            return await Task.Run<bool>( () =>
            {
                if ( _readStream != null && _writeStream != null )
                    throw new IOException( "Pipe connection was already initialized" );

                _connected = ConnectStream( out _writeStream, _pipeName + 'r' );
                _connected |= ConnectStream( out _readStream, _pipeName + 'w' );

                return _connected;
            } );
        }

        private void WaitForConnections()
        {
            if ( _writeStream != null && _readStream != null )
                throw new IOException( "Pipe connection was already initialized" );

            WaitForConnection( out _readStream, _pipeName + 'r' );
            WaitForConnection( out _writeStream, _pipeName + 'w' );

            _connected = _readStream.IsConnected && _writeStream.IsConnected;
            _opened = _connected;
        }

        public void BeginAccept( AsyncCallback callback, object state )
        {
            Action action = WaitForConnections;
            action.BeginInvoke( callback, state );
        }

        public void BeginReceive( byte[] buffer, AsyncCallback callback, object state )
        {
            _readStream.BeginRead( buffer, 0, buffer.Length, callback, state );
        }

        public int EndReceive( IAsyncResult result )
        {
            return _readStream.EndRead( result );
        }

        public void BeginSend( byte[] data, AsyncCallback callback, object state )
        {
            _writeStream.BeginWrite( data, 0, data.Length, callback, state );
        }

        public void EndSend( IAsyncResult result )
        {
            _writeStream.EndWrite( result );
        }

        private bool ConnectStream( out PipeStream stream, string pipeName )
        {
            stream = new NamedPipeClientStream( ".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous );
            ( stream as NamedPipeClientStream ).Connect();

            return stream.IsConnected;
        }

        private void WaitForConnection( out PipeStream stream, string pipeName )
        {
            stream = new NamedPipeServerStream( pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous );
            _opened = true;
            
            ( stream as NamedPipeServerStream ).WaitForConnection();
            _connected = true;
        }

        private void CloseStream( PipeStream stream )
        {
            stream.Close();
            stream.Dispose();
            stream = null;
        }

        public void Close()
        {
            _connected = false;
            _opened = false;

            CloseStream( _readStream );
            CloseStream( _writeStream );
        }
    }
}
